import {
  Object3D, Vector2, Vector3, Raycaster, MOUSE, Scene, Color, OrthographicCamera, WebGLRenderer, MeshLambertMaterial,
  Mesh, AmbientLight, DirectionalLight, Group, LineBasicMaterial, LineSegments, EdgesGeometry, SphereBufferGeometry
} from './three.custom.js';
import { clearObject3D, updateConfig } from './three.util.js'
import { GLTFLoader } from './GLTFLoader.js';
import CameraSpinControls from '../spin-controls/CameraSpinControls';
import threeConfig from '../../config/three.config';
import { sRGBEncoding } from 'three'

// parm_container：渲染区域
function createCloudChart(parm_container) {
  // 定义常量
  // 三维模型状态，与cameraSpinControls.js中的保持一致
  const STATE = { NONE: 0, ROTATE: 1, DOLLY: 2, PAN: 3, TOUCH_DOLLY_PAN: 4 };
  // 相机距离物体的距离，相对于物体的多少倍，若数值较小当旋转物体时容易接触相机造成部分不可见
  const CAMERA_OBJ_DISTANCE = 20;
  // 区分点击和拖动的参数：鼠标移动的width+height的最小距离（px）
  const CLICK_DRAG_DISTANCE = 3;
  // 是否启用阻尼（惯性），启用后会给旋转物体带来重量感，旋转后不会立即停下，而是缓慢停下，默认不启用
  const ENABLE_DAMPING = threeConfig.enableDamping ? threeConfig.enableDamping : false;
  // 配置鼠标按键组合
  const DEFAULT_MOUSE_KEYS = {
    ROTATE: { mouse: MOUSE.RIGHT, ctrlKey: true, shiftKey: false, altKey: false }, // 配置旋转
    DOLLY: { mouse: null, ctrlKey: false, shiftKey: false, altKey: false }, // 配置缩放
    PAN: { mouse: MOUSE.MIDDLE, ctrlKey: false, shiftKey: false, altKey: false }, // 配置平移
  };
  const MOUSE_KEYS = updateConfig(threeConfig.mousekeys, DEFAULT_MOUSE_KEYS);
  const SET_ROTATE_CENTER_MOUSE = MOUSE.RIGHT; // 配置哪个鼠标键进行旋转中心的设置
  // 模型名字
  const FACE_OBJ_NAME = 'faceObj';
  const LINE_OBJ_NAME = 'lineObj';
  const MAIN_OBJ_NAME = 'mainObj';
  const TRACKBALL_WIDGET_NAME = 'trackballWidget'
  // 事件名称
  const MOUSE_DOWN_EVENT_NAME = 'mousedown';
  const MOUSE_UP_EVENT_NAME = 'mouseup';

  // 定义变量
  let container = parm_container;
  let camera = null;
  let scene = null;
  let renderer = null;
  let cameraSpinControl = null;
  let loop = null;
  let gltfLoader = new GLTFLoader();
  let state = STATE.NONE; // 当前三维模型的状态
  // 设置旋转中心相关
  let currentRotateCenter = null; // 当前设置的旋转中心，为null表示默认的旋转中心：屏幕中心
  let startPoint = new Vector2();
  let raycaster = new Raycaster();

  // 初始化
  _init();

  // 监控渲染区域container变化
  const resezeObserver = new ResizeObserver(() => { _onWindowResize() });
  resezeObserver.observe(container);

  // ------------------------------------------------------------------------
  // public methods 外部可调用
  // ------------------------------------------------------------------------

  let ctx = new Object();
  // 加载gltf模型
  ctx.loadGltf = function loadGltf(modelURL) {
    return new Promise((resolve) => {
      _initModel(modelURL).then(() => {
        _animate();
        console.log('云图渲染完成');
        resolve();
      });
    });
  }

  // 工具条事件
  ctx.toolbarAction = function toolbarAction(action) {
    if (action === "frontView") {
      _showView(new Vector3(0, 0, 1)); // 主视图/前视图视角 xy
    }
    if (action === "topView") {
      _showView(new Vector3(0, 1, 0)); // 俯视图视角  xz
    }
    if (action === "leftView") {
      _showView(new Vector3(-1, 0, 0)); // 左视图视角 yz
    }
    if (action === "axonometricView") {
      _showView(new Vector3(1, 1, 1)); // 轴测图视角 xyz
    }
    if (action === "default") {
      _setRotateCenterMode(false);
    }
    if (action === "rotateCenter") {
      _setRotateCenterMode(true);
    }
  }

  // 清理模型
  ctx.clear = function clear() {
    clearObject3D(scene);
    renderer.dispose();
  }

  // 清缓存，dispose后需要创建新实例才可运行
  ctx.dispose = function dispose() {
    clearObject3D(scene);
    renderer.dispose();
    renderer.forceContextLoss();
    // renderer.content = null; //网上抄的，没有发现实质性作用，console.log(renderer.content)显示undefined
    renderer.domElement.remove(); // 删除renderer中新建的canvas元素
    renderer.domElement = null;
    renderer = null; // 这里面有一个buffergeometry，退出时需清除
    cancelAnimationFrame(loop);
  }

  // 重新绑定参数
  ctx.applyArguments = function applyArguments(parm_container) {
    container = parm_container;
    // 重新绑定renderer
    container.appendChild(renderer.domElement);
    // 重新绑定resezeObserver
    resezeObserver.disconnect();
    resezeObserver.observe(container);
  }

  return ctx;

  // ------------------------------------------------------------------------
  // internals 内部使用
  // ------------------------------------------------------------------------

  // 初始化
  function _init() {
    // 定义场景 scene
    scene = new Scene();
    scene.background = new Color(0xe0e0e0);

    // 定义相机 camera
    // 使用正交相机
    camera = new OrthographicCamera(container.clientWidth / -2, container.clientWidth / 2, container.clientHeight / 2, container.clientHeight / -2, 0, 1000000);
    camera.updateProjectionMatrix();

    // 设置光源
    const ambientLight = new AmbientLight(0xffffff, 2.2);
    scene.add(ambientLight);
    const directionalLight1 = new DirectionalLight(0xffffff, 2);
    directionalLight1.position.set(-0.5, 1, -0.5);
    scene.add(directionalLight1);
    const directionalLight2 = new DirectionalLight(0xffffff, 2);
    directionalLight2.position.set(0.5, -1, 0.5);
    scene.add(directionalLight2);

    // 定义渲染器 renderer
    renderer = new WebGLRenderer({ antialias: true }); // 抗锯齿antialias
    renderer.setSize(container.clientWidth, container.clientHeight);
    renderer.domElement.style.width = '100%';
    renderer.domElement.style.height = '100%';
    renderer.outputEncoding = sRGBEncoding; // 颜色空间转换为sRGB，更适应人眼的感光
    container.appendChild(renderer.domElement);

    // 定义控制器 cameraSpinControl
    cameraSpinControl = new CameraSpinControls(camera, renderer.domElement);
    if (camera.isOrthographicCamera) {
      // With orthographic projection calculations are more direct使正交相机下旋转更顺滑
      // using POINTER_SPHERE_MAPPING.HOLROYD or SHOEMAKE
      cameraSpinControl.spinControl.setPointerToSphereMapping(
        cameraSpinControl.spinControl.POINTER_SPHERE_MAPPING.SHOEMAKE
      );
    }
    cameraSpinControl.startTrackballScreenCenter = false;
    cameraSpinControl.setEnableDamping(ENABLE_DAMPING); // 配置是否启用阻尼（惯性）
    cameraSpinControl.setMouseKeys(MOUSE_KEYS); // 配置控制器中的mouse keys
    // 监控控制器变化
    cameraSpinControl.addEventListener('start', (event) => { state = event.state });
    cameraSpinControl.addEventListener('change', () => {
      // 平移时旋转中心点跟着物体走
      if (state === STATE.PAN) {
        if (currentRotateCenter !== null) {
          cameraSpinControl.setTargetPosition(currentRotateCenter);
        }
      }
    });
    cameraSpinControl.addEventListener('end', (event) => { state = event.state });

    renderer.render(scene, camera); // 渲染画布
  }

  // 是否进入设置旋转中心模式
  function _setRotateCenterMode(enabled) {
    if (enabled) {
      scene.getObjectByName(TRACKBALL_WIDGET_NAME).visible = true;
      // 添加设置旋转中心事件
      _addSetRotateCenterEventListener();
    } else {
      scene.getObjectByName(TRACKBALL_WIDGET_NAME).visible = false;
      // 移除设置旋转中心事件
      _removeSetRotateCenterEventListener();
      currentRotateCenter = null;

      // 若物体在屏幕中间，则将旋转中心设为物体中心，若物体不在屏幕中心，则将旋转中心设为屏幕中心
      // 将物体中心坐标转标准设备坐标
      const geometryCenter = scene.getObjectByName(FACE_OBJ_NAME).geometry.boundingSphere.center;
      const stdVector = geometryCenter.clone().project(camera);
      // if 坐标约为（0，0）
      if (Math.abs(stdVector.x) < 0.01 && Math.abs(stdVector.y) < 0.01) {
        // 将旋转中心设置为物体中心
        cameraSpinControl.setTargetPosition(geometryCenter);
      } else {
        // 将旋转中心设置为屏幕中心
        cameraSpinControl.target.setFromMatrixPosition(cameraSpinControl.trackballToObject);
        const startDistance = cameraSpinControl.target.length();
        cameraSpinControl.target.set(0, 0, -startDistance);
        cameraSpinControl.target.applyQuaternion(camera.quaternion);
        cameraSpinControl.target.add(camera.position);
        cameraSpinControl.setTargetPosition(cameraSpinControl.target);
      }
    }
  }

  // 多视图视角
  function _showView(viewpoint) {
    const geometry = scene.getObjectByName(FACE_OBJ_NAME).geometry;
    camera.position.set(
      geometry.boundingSphere.center.x + viewpoint.x * CAMERA_OBJ_DISTANCE * geometry.boundingSphere.radius,
      geometry.boundingSphere.center.y + viewpoint.y * CAMERA_OBJ_DISTANCE * geometry.boundingSphere.radius,
      geometry.boundingSphere.center.z + viewpoint.z * CAMERA_OBJ_DISTANCE * geometry.boundingSphere.radius
    );
    camera.lookAt(geometry.boundingSphere.center);
    // 视角的改变不影响旋转中心的变化
    if (currentRotateCenter !== null) {
      cameraSpinControl.setTargetPosition(currentRotateCenter);
    } else {
      cameraSpinControl.setTargetPosition(geometry.boundingSphere.center);
    }
    _fullscreen();
  }

  // 自适应屏幕大小（使用 包围球确定高宽缩放比例 方式）
  function _fullscreen() {
    const maxDiameter = scene.getObjectByName(FACE_OBJ_NAME).geometry.boundingSphere.radius * 2;
    // 画布宽度 是 包围球直径 的widthZoom倍， 即包围球缩放widthZoom倍 宽度刚好填满画布
    const widthZoom = container.clientWidth / maxDiameter;
    // 画布高度 是 包围球直径 的heightZoom倍，即包围球缩放heightZoom倍 高度刚好填满画布
    const heightZoom = container.clientHeight / maxDiameter;
    // 取两者缩放比例较小值，设置为正交相机的缩放倍数
    if (widthZoom >= heightZoom) {
      camera.zoom = heightZoom;
    } else {
      camera.zoom = widthZoom;
    }
    camera.updateProjectionMatrix();
  }

  // 初始化三维模型
  function _initModel(url) {
    return new Promise((resolve) => {
      // 加载gltf模型
      gltfLoader.load(url, (gltf) => {
        const faceGeometry = gltf.scene.children[0].geometry;

        // 材质设置
        const facesmaterails = gltf.scene.children[0].material; // 面的初始化材质
        // 点击面的材质 0xd83c3c 0x3fb3bf 0x28aaaa vertexColors: THREE.FaceColors
        // const choosematerial = new MeshLambertMaterial({ color: 0xd83c3c, side: DoubleSide, transparent: true, opacity: 1 });
        // 计算法向量，设置光源材质需要
        faceGeometry.computeVertexNormals();

        // object
        const face = new Mesh(faceGeometry, facesmaterails);
        face.name = FACE_OBJ_NAME;
        const line = gltf.scene.children[1];
        line.name = LINE_OBJ_NAME;
        const obj = new Object3D();
        obj.add(face);
        obj.add(line);
        obj.name = MAIN_OBJ_NAME;

        // 将几何体加入到scene中
        scene.add(obj);
        console.log(scene);

        // 设置相机初始角度为轴测图角度
        faceGeometry.computeBoundingBox();
        faceGeometry.computeBoundingSphere();
        _showView(new Vector3(1, 1, 1));

        // 定义轨道球UI
        const trackballWidget = new Group();
        trackballWidget.name = TRACKBALL_WIDGET_NAME;
        const trackballRadius = faceGeometry.boundingSphere.radius / 200; // 可以调整轨道球半径的大小
        const geometry = new SphereBufferGeometry(trackballRadius, 8, 8);
        const meshMaterial = new MeshLambertMaterial({ color: 0xde2df0 });
        const lineMaterial = new LineBasicMaterial({ color: 0x952df0 });
        trackballWidget.add(new LineSegments(new EdgesGeometry(geometry), lineMaterial));
        trackballWidget.add(new Mesh(geometry, meshMaterial));
        scene.add(trackballWidget);
        trackballWidget.visible = false;

        resolve();
      });
    });
  }

  // 循环渲染
  function _animate() {
    loop = requestAnimationFrame(_animate);
    cameraSpinControl.update();
    if (scene.getObjectByName(TRACKBALL_WIDGET_NAME)) {
      scene.getObjectByName(TRACKBALL_WIDGET_NAME).position.copy(cameraSpinControl.target);
    }
    renderer.render(scene, camera);
  }

  // 屏幕坐标（clientX, clientY） => 标准化设备坐标（Normalised Device Coordinates，NDC），相对于中心点
  function clientCoordinatesToNDC(clientX, clientY) {
    return new Vector2(
      ((clientX - renderer.domElement.getBoundingClientRect().left) / renderer.domElement.width) * 2 - 1,
      -((clientY - renderer.domElement.getBoundingClientRect().top) / renderer.domElement.height) * 2 + 1
    )
  }

  // ------------------------------------------------------------------------
  // listeners
  // ------------------------------------------------------------------------

  // 事件监听：设置旋转中心
  function _addSetRotateCenterEventListener() {
    renderer.domElement.addEventListener(MOUSE_DOWN_EVENT_NAME, _setRotateCenterStart);
    renderer.domElement.addEventListener(MOUSE_UP_EVENT_NAME, _setRotateCenterOver);
  }

  // 移除事件监听：设置旋转中心
  function _removeSetRotateCenterEventListener() {
    renderer.domElement.removeEventListener(MOUSE_DOWN_EVENT_NAME, _setRotateCenterStart);
    renderer.domElement.removeEventListener(MOUSE_UP_EVENT_NAME, _setRotateCenterOver);
  }

  // 设置旋转中心时，鼠标点击按下事件
  function _setRotateCenterStart(event) {
    if (event.button === SET_ROTATE_CENTER_MOUSE) {
      startPoint.x = event.clientX;
      startPoint.y = event.clientY;
    }
  }

  // 设置旋转中心时，鼠标点击结束事件
  function _setRotateCenterOver(event) {
    // 只有鼠标右键单击可以设置旋转中心
    if (event.button === SET_ROTATE_CENTER_MOUSE) {
      // 区分点击和拖动
      if (Math.abs(startPoint.x - event.clientX) + Math.abs(startPoint.y - event.clientY) <= CLICK_DRAG_DISTANCE) {
        console.log('设置旋转中心');
        // 屏幕坐标 转 标准化设备坐标
        const mouse = clientCoordinatesToNDC(event.clientX, event.clientY);
        // 将点击选中的element的中心设置为旋转中心
        raycaster.setFromCamera(mouse, camera);
        const intersects = raycaster.intersectObjects([scene.getObjectByName(FACE_OBJ_NAME)]);
        if (intersects.length > 0) {
          currentRotateCenter = intersects[0].point;
          cameraSpinControl.setTargetPosition(currentRotateCenter);
        }
      }
    }
  }

  // 屏幕变化事件
  function _onWindowResize() {
    if (container.clientWidth !== 0 && container.clientHeight !== 0) {
      // 重新设置相机向下左右侧面，保证模型不变形
      camera.left = container.clientWidth / -2;
      camera.right = container.clientWidth / 2;
      camera.top = container.clientHeight / 2;
      camera.bottom = container.clientHeight / -2;
      camera.updateProjectionMatrix();

      // 添加setTimeout是防止有时候canvas大小变化时闪现黑屏
      setTimeout(() => {
        // 重新设置render的size，第三个参数设置为false以阻止对canvas的样式做任何改变
        renderer.setSize(container.clientWidth, container.clientHeight, false);
        // 重新设置旋转控制器
        cameraSpinControl.onWindowResize();
      });
    }
    // console.log('renderer:', renderer.domElement.width, renderer.domElement.height);
    // console.log('container:', container.clientWidth, container.clientHeight);
  }
}

// 单例模式
createCloudChart.getInstance = (function () {
  let instance = null;
  return function (container) {
    if (!instance) {
      instance = createCloudChart(container);
    } else {
      instance.applyArguments(container);
    }
    return instance;
  }
})()

export { createCloudChart };